package jp.wug.core;

import com.google.inject.*;
import com.google.inject.assistedinject.FactoryProvider;
import com.google.inject.util.Types;

import java.lang.reflect.ParameterizedType;
import java.util.HashMap;
import java.util.Map;

public class KeyedObjectModule extends AbstractModule {
    private final Map<TypeLiteral<?>, KeyValueType<?, ?>> keyValueTypes = new HashMap<TypeLiteral<?>, KeyValueType<?, ?>>();

    private class KeyValueType<K, V> {
        private final TypeLiteral<K> keyType;
        private final TypeLiteral<V> valueType;
        private Provider<KeyedObjectFactory<K, V>> factoryProvider = new Provider<KeyedObjectFactory<K, V>>() {
            public KeyedObjectFactory<K, V> get() {
                throw new AssertionError("KeyedObjectModule not initialized properly");
            }
        };

        private KeyValueType(TypeLiteral<K> keyType, TypeLiteral<V> valueType) {
            this.keyType = keyType;
            this.valueType = valueType;
        }

        void init(Injector injector) {
            factoryProvider = injector.getProvider(Key.get(getRawFactoryType()));
        }

        private void bindFactory() {
            TypeLiteral<KeyedObjectFactory<K, V>> rawFactoryType = getRawFactoryType();
            Provider<KeyedObjectFactory<K, V>> provider = FactoryProvider.newFactory(rawFactoryType, valueType);
            bind(rawFactoryType).toProvider(provider);
        }

        Provider<KeyedObjectFactory<String, Model>> getFactoryProvider(ModelFactory modelFactory) {
            Translators.Translator<String, K> keyTranslator = Translators.getTranslator(TypeLiteral.get(String.class), keyType);
            return new TranslatingKeyedObjectFactoryProvider(keyTranslator, modelFactory);
        }

        private class TranslatingKeyedObjectFactoryProvider implements Provider<KeyedObjectFactory<String, Model>> {
            private final Translators.Translator<String, K> translator;
            private final ModelFactory modelFactory;

            private TranslatingKeyedObjectFactoryProvider(Translators.Translator<String, K> translator, ModelFactory modelFactory) {
                this.translator = translator;
                this.modelFactory = modelFactory;
            }

            public KeyedObjectFactory<String, Model> get() {
                return new TranslatingKeyedObjectFactory<K, V>(factoryProvider.get(), translator, modelFactory);
            }
        }

        @SuppressWarnings("unchecked")
        TypeLiteral<KeyedObjectFactory<K, V>> getRawFactoryType() {
            ParameterizedType rawFactory = Types.newParameterizedType(KeyedObjectFactory.class, keyType.getType(), valueType.getType());
            return (TypeLiteral<KeyedObjectFactory<K, V>>) TypeLiteral.get(rawFactory);
        }
    }

    void init(Injector injector) {
        for (KeyValueType<?, ?> keyValueType : keyValueTypes.values()) {
            keyValueType.init(injector);
        }
    }

    public <K, V> void add(TypeLiteral<K> keyType, TypeLiteral<V> valueType) {
        if (keyValueTypes.containsKey(valueType)) {
            throw new IllegalArgumentException("duplicated factory for type: " + valueType);
        }
        KeyValueType<K, V> keyValueType = new KeyValueType<K, V>(keyType, valueType);
        keyValueTypes.put(valueType, keyValueType);
    }

    @SuppressWarnings("unchecked")
    private <V> KeyValueType<?, V> getKeyValueType(TypeLiteral<V> valueType) {
        return (KeyValueType<?, V>) keyValueTypes.get(valueType);
    }

    public <V> Provider<KeyedObjectFactory<String, Model>> getFactoryProvider(TypeLiteral<V> valueType, ModelFactory modelFactory) {
        KeyValueType<?, V> type = getKeyValueType(valueType);
        return type.getFactoryProvider(modelFactory);
    }

    @Override
    protected void configure() {
        for (KeyValueType k : keyValueTypes.values()) {
            k.bindFactory();
        }
    }

    private static class TranslatingKeyedObjectFactory<K, V> implements KeyedObjectFactory<String, Model> {
        private final KeyedObjectFactory<K, V> base;
        private final Translators.Translator<String, K> translator;
        private final ModelFactory modelFactory;

        private TranslatingKeyedObjectFactory(KeyedObjectFactory<K, V> base, Translators.Translator<String, K> translator, ModelFactory modelFactory) {
            this.base = base;
            this.translator = translator;
            this.modelFactory = modelFactory;
        }

        public Model create(String key) {
            K k = translator.translate(key);
            V v = base.create(k);
            return modelFactory.create(v);
        }
    }
}